# pylint: disable-msg=W0622,E1101
"""
@author: Sergiy Tsymbalyuk
"""

import re
from datetime import date, datetime

from pylons import request, url, tmpl_context as c
from pylons.controllers.util import redirect
from pylons.i18n import lazy_ugettext as _
from pylons.decorators import jsonify

from babel import dates

from sqlalchemy.orm import joinedload

from chowa.lib.site.forms import TabularFieldSet
from chowa.lib.site.navigation import Tab
from chowa.lib.base import BaseController, render, Session


class BaseCrudController(BaseController):
    __secondary_tabs__ = [
        [
            Tab(caption=_('View'),
                params=dict(action='index')),
            Tab(caption=_('Add'),
                params=dict(action='add'))]
    ]
    
    def delete(self, relation=None, id=None):
        record_id = request.params.get('record_id')
        record = Session.query(self.__model__).get(record_id)
        if request.method == 'POST':
            if request.params.has_key('delete'):
                Session.delete(record)
                Session.commit()
                return redirect(request.params.get('referer'))
        c.record = record
        return render('/crud/delete.html')


class FlatCrudController(BaseCrudController):
    __columns__ = []
    __order__ = []
    __join__ = []
    __edit_columns__ = None
    __references__ = {}
    __view_url__ = None
    __bool_values__ = {}
    __index_header__ = {}
    __main_heading__ = ''
    
    def __before__(self):
        self.__secondary_tabs__ = [
            [
                Tab(caption=_('View'),
                    params=dict(action='index')),
                Tab(caption=_('Add'),
                    params=dict(action='add'))]
        ]
        
        c.main_heading = self.__main_heading__
        
        def get_field(record, column):
            name = column['name']
            tokens = name.split('.')
            obj = record
            for token in tokens:
                obj = getattr(obj, token)
            values = self.__bool_values__.get(name)
            if values:
                if obj is True: obj = values[0]
                elif obj is False: obj = values[1]
                else: obj = values[2]
            if type(obj) == date:
                obj = dates.format_date(obj, format='short')
            elif type(obj) == datetime:
                obj = dates.format_datetime(obj, format='short')
            return obj
        c.get_field = get_field
    
    def index(self, relation=None, id=None):
        query = Session.query(self.__model__)
        if relation and id:
            relation_property = getattr(self.__model__, relation).property
            local_side_id = relation_property.local_side[0]
            query = query.filter(local_side_id == id)
            c.main_heading = self._get_related_object(relation, id)
        
        column_names = []
        
        if self.__columns__:
            column_names = list(self.__columns__)
        else:
            column_names = self.__model__._sa_class_manager.keys()
        
        if self.__join__:
            query = query.join(*self.__join__)
        if self.__order__:
            query = query.order_by(*self.__order__)
        # FIXME: show all data when no relation filter
        c.records = query.all()
        
        try:
            column_names.remove(str(relation))
        except:
            pass
            
        
        # Find verbose name of column, otherwise use table column name.
        c.columns = []
        table_columns = self.__model__.__table__.columns
        for column_name in column_names:
            column = None
            if table_columns.has_key(column_name):
                column = table_columns.get(column_name)
                if column.foreign_keys: continue
                if column.primary_key: continue
            else:
                tokens = column_name.split('.')
                if len(tokens) == 1:
                    column = getattr(self.__model__, tokens[0])
                    if hasattr(column.property, 'local_side'):
                        column = column.property.local_side[0]
                elif len(tokens) == 2:
                    target = getattr(self.__model__, tokens[0]).property.target
                    column = target.columns.get(tokens[1])
                    if column is None: column = target.columns.get(tokens[1] + '_id')
            
            verbose = column_name
            if column_name in self.__index_header__:
                verbose = self.__index_header__[column_name]
            elif hasattr(column, 'info'):
                if column.info.has_key('verbose'):
                    verbose = column.info['verbose']
            c.columns.append(dict(name=column_name, verbose=verbose))
        
        # Record urls
        c.references = {}
        for column in self.__references__:
            remote_name = getattr(self.__model__, column).property.remote_side[0].name
            if remote_name == 'id':
                c.references[column] = lambda record: url(controller=self.__references__[column],
                                                          action='edit',
                                                          id=getattr(record, column).id)
            else:
                filter_name = 'filter__%s' % remote_name
                c.references[column] = lambda record: url(**{'controller': self.__references__[column],
                                                             'action': 'index',
                                                             filter_name: record.id})
        c.view_url = self.__view_url__
        c.editing_url = lambda record: url.current(action='edit', record_id=record.id)
        c.deleting_url = lambda record: url.current(action='delete', record_id=record.id)
        
        return render('/crud/index.html')
    
    def add(self, relation=None, id=None):
        model = self.__model__
        fs = TabularFieldSet(model, session=Session)
        if hasattr(self, '__filters__'):
            for filter in self.__filters__:
                filter_model, filter_field = self.__filters__[filter]
                filter_fs = TabularFieldSet(filter_model, Session)
                fs.engine.filters[filter] = getattr(filter_fs, filter_field)
        if relation and id:
            related_object = self._get_related_object(relation, id)
            data = fs.to_dict()
            data[getattr(fs, relation).renderer.name] = id
            fs.rebind(data=data)
            c.main_heading = related_object
            options = [getattr(fs, relation).hidden()]
            fs.configure(options=options)
        if self.__edit_columns__:
            fs.configure(include=[getattr(fs, column) for column in self.__edit_columns__])
        if request.method == 'POST':
            if self._save(fs):
                self._after_adding(fs.model.id)
                return redirect(url.current(action='index'))
        c.form = fs.render()
        return render('/crud/add.html')
    
    def _after_adding(self, id): pass
    
    def edit(self, relation=None, id=None):
        record_id = request.params.get('record_id')
        record = Session.query(self.__model__).get(record_id)
        self.__secondary_tabs__[0] += [Tab(caption=_('Edit'), 
                                           params=dict(action='edit'))]
        c.main_heading = unicode(record)
        fs = TabularFieldSet(record)
        if hasattr(self, '__filters__'):
            for filter in self.__filters__:
                filter_model, filter_field = self.__filters__[filter]
                filter_record = Session.query(filter_model).get(getattr(record, filter))
                filter_fs = TabularFieldSet(filter_record)
                fs.engine.filters[filter] = getattr(filter_fs, filter_field)
        if relation and id:
            related_object = getattr(record, relation)
            c.main_heading = unicode(related_object)
        if self.__edit_columns__:
            fs.configure(include=[getattr(fs, column) for column in self.__edit_columns__ if column <> relation])
        if request.method == 'POST':
            if self._save(fs):
                return redirect(url.current(action='index'))
        c.form = fs.render()
        return render('/crud/edit.html')
    
    @jsonify
    def get_ids(self, id):
        field_name = request.params.get('field')
        if hasattr(self, '__filters__'):
            model, filter = self.__filters__.get(field_name)
            filter_field = getattr(model, filter).property.local_side[0]
            query = Session.query(model).filter(filter_field == id)
            return [record.id for record in query]
        return []
    
    def _save(self, fs):
        fs.rebind(data=request.POST)
        if fs.validate():
            fs.sync()
            try:
                Session.commit()
                return True
            except:
                Session.rollback()
                return False
    
    def _get_related_object(self, relation, id):
        relation_property = getattr(self.__model__, relation).property
        remote_side_id = relation_property.remote_side[0]
        query = Session.query(relation_property.mapper.class_)
        related_object = query.filter(remote_side_id==id).one()
        return related_object 

    
class HierarchyCrudController(BaseCrudController):
        
    def index(self, id=0):
        id = int(id)
        c.parent_id = 0
        c.item = 'ROOT'
        if id > 0:
            model = self.__sub_model__
            query = Session.query(model).filter_by(id=id)
            if query.count() == 1:
                item = query.one()
                c.parent_id = item.parent.id
            
            query = Session.query(self.__model__).filter_by(id=id)
            c.item = query.one()
        else:
            model = self.__top_model__
        filters = {}
        # Get filter from request.
        for param in request.params:
            filter_match = re.match('filter__', param)
            if not filter_match is None:
                field = param.replace('filter__', '')
                filters[field] = request.params.get(param)
        query = Session.query(model)
        if len(filters) > 0: query = query.filter_by(**filters)
        if id > 0: query = query.filter_by(**{self.__parent_filter__: id})
        c.records = query.all()
        
        c.subindex_url = lambda record: url.current(action='index',id=record.id)
        c.editing_url = lambda record: url.current(action='edit', id=record.id)
        c.deleting_url = lambda record: url.current(action='delete', id=record.id)
        return render('/crud/hindex.html')
    
    def add(self, id=0):
        if id > 0:
            model = self.__sub_model__
        else:
            model = self.__top_model__
        
        fs = TabularFieldSet(model, session=Session)
        exclude = [fs.subitems]
        if id > 0:
            exclude += [fs.parent]
        fs.configure(exclude=exclude)
        if request.method == 'POST':
            fs = TabularFieldSet(model, session=Session, data=request.params)
            fs.sync()
            fs.model.parent_id = id
            Session.commit()
            return redirect(url.current(action='index'))
        c.form = fs.render()
        return render('/crud/hadd.html')
    
    def edit(self, id):
        record = Session.query(self.__model__).filter_by(id=id).one()
        fs = TabularFieldSet(record)
        if request.method == 'POST':
            fs = TabularFieldSet(record, data=request.params)
            fs.sync()
            Session.commit()
            return redirect(url.current(action='index'))
        c.form = fs.render()
        return render('/crud/hedit.html')


class ViewEditController(BaseController):
    __view_template__ = '/crud/ve/view.html'
    __edit_columns__ = None
    __secondary_tabs__ = [
        [
            Tab(caption=_('View'),
                params=dict(action='view')),
            Tab(caption=_('Edit'),
                params=dict(action='edit'))]
    ]
    
    def __before__(self, id):
        c.record = Session.query(self.__model__).get(id)
        c.main_heading = unicode(c.record)

    def view(self, id):
        return render(self.__view_template__)
    
    def edit(self, id):
        c.record = Session.query(self.__model__).get(id)
        fs = TabularFieldSet(c.record)
        if self.__edit_columns__:
            fs.configure(include=[getattr(fs, column) for column in self.__edit_columns__])
        if request.method == 'POST':
            if self._save(fs):
                return redirect(url.current(action='view'))
        c.form = fs.render()
        return render('/crud/ve/edit.html')
    
    def _save(self, fs):
        fs.rebind(data=request.POST)
        if fs.validate():
            fs.sync()
            try:
                Session.commit()
                return True
            except:
                Session.rollback()
                return False
